//
// Created by 郝羽 on 2017/12/8.
//

#ifndef RTC_XSWITCH2_BITRATE_HPP
#define RTC_XSWITCH2_BITRATE_HPP

#include <cstdint>

template <int64_t WindowLengthMs>
class Bitrate {
public:
    Bitrate() {
        for (int i = 0; i < WindowLengthMs; i++) {
            buckets_[i].samples = 0;
            buckets_[i].bytes = 0;
        }
    };

    void update(uint32_t bytes, int64_t now_ms) {
        if (now_ms < oldest_ms_) {
            return;
        }
        eraseOld(now_ms);
        if (oldest_ms_ == -WindowLengthMs) {
            oldest_ms_ = now_ms;
        }
        uint32_t offset = static_cast<uint32_t>(now_ms - oldest_ms_);
        uint32_t i = oldest_i_ + offset;
        if (i >= WindowLengthMs) {
            i -= WindowLengthMs;
        }
        buckets_[i].bytes += bytes;
        buckets_[i].samples += 1;
        bytes_ += bytes;
        samples_ += 1;
    };

    bool read(uint32_t& bps, int64_t now_ms) {
        eraseOld(now_ms);
        int64_t window_ms = now_ms - oldest_ms_ + 1;
        if (samples_ == 0 || window_ms <= 1 || (samples_ <= 1 && window_ms < window_ms_)) {
            return false;
        }
        float scale = scale_ / window_ms;
        bps = static_cast<uint32_t>(bytes_ * scale + 0.5f);
        return true;
    };

    void reset() {
        bytes_ = 0;
        samples_ = 0;
        oldest_ms_ = -WindowLengthMs;
        oldest_i_ = 0;
        window_ms_ = WindowLengthMs;
        for (int i = 0; i < WindowLengthMs; i++) {
            buckets_[i].samples = 0;
            buckets_[i].bytes = 0;
        }
    }
private:
    void eraseOld(int64_t now_ms) {
        if (!initialized()) {
            return;
        }
        int64_t new_oldest_ms = now_ms - window_ms_ + 1;
        if (new_oldest_ms < oldest_ms_) {
            return;
        }
        while (oldest_ms_ < new_oldest_ms) {
            Bucket& b = buckets_[oldest_i_];
            bytes_ -= b.bytes;
            samples_ -= b.samples;
            b.bytes = 0;
            b.samples = 0;
            oldest_i_ += 1;
            if (oldest_i_ >= WindowLengthMs) {
                oldest_i_ = 0;
            }
            oldest_ms_ += 1;
        }
        oldest_ms_ = new_oldest_ms;
    };

    bool initialized() {
        return oldest_ms_ != -WindowLengthMs;
    };

    class Bucket {
    public:
        size_t bytes = 0;
        size_t samples = 0;
    };

private:
    const int scale_ = 8000;
    Bucket buckets_[WindowLengthMs];
    uint32_t bytes_ = 0;
    int64_t oldest_ms_ = -WindowLengthMs;
    uint32_t oldest_i_ = 0;
    int64_t window_ms_ = WindowLengthMs;
    int samples_ = 0;
};

#endif //RTC_XSWITCH2_BITRATE_HPP
